<?php
declare(strict_types=1);

namespace LowCode;

use Closure;
use LowCode\Abstracts\Core;
use LowCode\Support\JsApi;
use LowCode\Support\JsDs;
use LowCode\Support\JsExp;
use LowCode\Support\JsFn;
use LowCode\Support\JsWatch;
use LowCode\Traits\HasCall;
use LowCode\Traits\HasLifeCycle;

/**
 * @method static Block make(string $name, Closure $children = null); 创建板块
 * @method $this id(string $id) 设置组件板块ID
 * @method $this name(string $name) 设置组件板块名称
 * @method $this locked(bool $bool = true | false) 是否锁定
 * @method $this css(string $css) 设置组件板块css
 * @method $this props(array $props) 设置组件props
 * @method $this emits(array $emits) 设置组件emits
 * @method $this lifeCycles(array $lifeCycles) 设置组件lifeCycles
 * @method $this methods(array $methods) 设置组件methods
 * @method $this slots(array $slots) 设置组件插槽
 * @method $this dataSources(array $dataSources) 设置组件资源
 * @property $id
 */
class Block extends Core
{
    protected array $attributes = [
        "name"=>"",
        "props"=>[]
    ];

    protected static array $allowAttributes = [
        "id","name","locked","css","props","emits","lifeCycles","methods","computed","slots","dataSources"
    ];

    protected Store $store;

    use HasCall, HasLifeCycle;

    /**
     * @param string $name
     * @param Closure|null $children
     */
    public function __construct(string $name, Closure $children = null)
    {
        $id = autoId("block");
        parent::__construct(["id"=>$id]);
        $this->store = Store::make($id);
        $this->inject("appConfig");
        $this->inject("globals");
        $this->name($name?:class_basename($this));
        $children!=null && call_user_func($children, $this);
    }

    /**
     * 设置node节点
     * @param Node $node
     * @return $this
     */
    public function node(Node $node): static
    {
        $nodes = $this->get("nodes",[]);
        $nodes[] = $node;
        return $this->nodes($nodes);
    }

    /**
     * 设置node节点
     * @param array|Node[]|Node $nodes
     * @return $this
     */
    public function nodes(Node|array $nodes): static
    {
        $preNodes = $this->get("nodes",[]);
        if ($nodes instanceof Node){
            $this->states($nodes->getState());
            $nodes = array_merge($preNodes, [$nodes]);
        }else{
            /**
             * @var Node $node
             */
            foreach ($nodes as $node) {
                $this->states($node->getState());
            }
            $nodes = array_merge($preNodes, $nodes);
        }
        return $this->setAttribute("nodes",$nodes);
    }

    /**
     * 设置state状态数据
     * @param string|array $key
     * @param mixed|JsExp|JsFn|null $value
     * @return $this
     */
    public function state(string|array $key, mixed $value = null): static
    {
        $state = $this->get("state",[]);
        if (is_array($key)){
            $state = array_merge($state, $key);
        }else{
            $state[$key] = $value;
        }
        return $this->setAttribute("state",$state);
    }

    /**
     * 设置state状态数据
     * @param array $states
     * @return $this
     */
    public function states(array $states): static
    {
        $state = $this->get("state",[]);
        $state = array_merge($state, $states);
        return $this->setAttribute("state",$state);
    }

    /**
     * 删除state其中某个属性状态数据
     * @param string $key
     * @return $this
     */
    public function removeState(string $key): static
    {
        $state = $this->get("state",[]);
        if(isset($state[$key])){
            unset($state[$key]);
        }
        return $this->setAttribute("state",$state);
    }


    /**
     * 设置 props属性
     * @param string $name
     * @param string|array $type 'String' | 'Number' | 'Boolean' | 'Array' | 'Object' | 'Function' | 'Date'
     * @param bool $required
     * @param mixed|JsExp|null $default 可使用Js
     * @return $this
     */
    public function prop(string $name, string|array $type = 'String' | 'Number' | 'Boolean' | 'Array' | 'Object' | 'Function' | 'Date' , bool $required = false, mixed $default = null): static
    {
        $props = $this->get("props",[]);
        $props[] = [
            "name"=>$name,
            "type"=>$type,
            "required"=>$required,
            "default"=> $default,
        ];
        return $this->props($props);
    }

    /**
     * 设置 emit事件属性
     * @param string $name
     * @param string[] $params
     * @return $this
     */
    public function emit(string $name, array $params = []): static
    {
        $emits = $this->get("emits",[]);
        $emits[] = [
            "name"=>$name,
            "params"=>$params,
        ];
        return $this->emits($emits);
    }

    /**
     * 添加组件方法method
     * @param string $name 方法名称
     * @param string $value js函数脚本
     * @param string|null $id
     * @return $this
     */
    public function method(string $name, string $value, string $id = null): static
    {
        $methods = $this->get("methods",[]);
        $methods[$name] = JsFn::make($value, $id);
        return $this->methods($methods);
    }


    /**
     * 添加组件注入
     * @param string $name
     * @param mixed|null $default
     * @param string|JsExp $from
     * @return $this
     */
    public function inject(string $name, mixed $default = null, string|JsExp $from = ""): static
    {
        $inject = ["name"=>$name,"default"=>$default];
        if ($from){
            $inject["from"] = $from;
        }
        $injectList = $this->get("inject",[]);
        $injectList[] = $inject;
        return $this->setAttribute("inject",$injectList);
    }

    /**
     * 添加组件注入
     * @param array $injects
     * @return $this
     */
    public function injects(array $injects): static
    {
        $injectList = $this->get("inject",[]);
        $injectList[] = $injects;
        return $this->setAttribute("inject",$injectList);
    }

    /**
     * 添加组件watch
     * @param JsFn|JsExp $source
     * @param JsFn|string $handler
     * @param array $options
     * @return $this
     */
    public function watch(JsFn|JsExp $source, JsFn|string $handler = "(newVal, oldVal)=>{}", array $options = []): static
    {
        $watchList = $this->get("watch",[]);
        if (is_string($handler)){
            $handler = JsFn::make($handler);
        }
        $watchList[] = JsWatch::make($source, $handler, $options);
        return $this->setAttribute("watch",$watchList);
    }

    /**
     * 添加组件watch
     * @param array $watchList
     * @return $this
     */
    public function watchList(array $watchList): static
    {
        $options = $this->get("watch",[]);
        $options = array_merge($options, $watchList);
        return $this->setAttribute("watch",$options);
    }

    /**
     * 添加组件计算属性computed
     * @param string $name
     * @param JsFn|string $value
     * @param string|null $id
     * @return $this
     */
    public function computed(string $name, JsFn|string $value, string $id = null): static
    {
        $computed = $this->get("computed",[]);
        $computed[$name] = is_string($value)? jsFn($value, $id): $value;
        return $this->setAttribute("computed", $computed);
    }

    /**
     * 添加组件计算属性computed
     * @param array $computedList
     * @return $this
     */
    public function computedList(array $computedList): static
    {
        $options = $this->get("computed",[]);
        $options = array_merge($options, $computedList);
        return $this->setAttribute("computed",$options);
    }

    /**
     * 设置 slot插槽属性
     * @param string $name
     * @param string[] $params
     * @return $this
     */
    public function slot(string $name, array $params = []): static
    {
        $slots = $this->get("slots",[]);
        $slots[] = [
            "name"=>$name,
            "params"=>$params,
        ];
        return $this->slots($slots);
    }

    /**
     * 添加组件源
     * @param string $name
     * @param JsDs $dataSource
     * @return $this
     */
    public function dataSource(string $name, JsDs $dataSource): static
    {
        $dataSources = $this->get("dataSources",[]);
        $dataSource->name($name);
        $dataSources[$name] = $dataSource;
        return $this->dataSources($dataSources);
    }

    /**
     * @param JsApi $jsApi
     * @return $this
     */
    public function addApi(JsApi $jsApi): static
    {
        $this->store->id($this->id)->addApi($jsApi);
        return $this;
    }

    /**
     * @param string $name
     * @param $url
     * @param callable|null $callable
     * @return $this
     */
    public function addDataSourceFromApi(string $name, $url, callable $callable = null): static
    {
        $api = JsApi::make($name,$url);
        $jsDs = JsDs::make($api->id);
        if ($callable!=null){
            call_user_func($callable, $api, $jsDs);
        }
        return $this->addApi($api)->dataSource($name,$jsDs);
    }

    /**
     * @param Block $block
     * @return $this
     */
    public function addBlock(Block $block): static
    {
        $this->store->id($this->id)->addBlock($block);
        return $this;
    }

    /**
     * @return Store
     */
    public function getStore(): Store
    {
        return $this->store->id($this->id);
    }

    /**
     * @return array
     */
    public function toArray(): array
    {
        Store::make("global")->buildBlock($this);
        if ($this->id){
            Store::make($this->id)->buildBlock($this);
        }
        return parent::toArray();
    }


    public function __call($method, $parameters)
    {
        return $this->toCall($method, $parameters);
    }
}
